package fr.ul.ade.secunde.sync;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.database.Cursor;
import android.database.MatrixCursor;
import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
import fr.ul.ade.secunde.util.AdeWebCollections;
import fr.ul.ade.secunde.util.LessonConstants;

/**
 * Classe analysant les responses XML de l'API d'ADE.
 * 
 * @author Michaël Schnell
 * @since Jul 29, 2013
 * 
 * @version 1.3
 */
public class AdeWebXmlParser {

	private static final Long DEFAULT_ID = Long.valueOf(0);
	private static final Integer DEFAULT_INTEGER = Integer.valueOf(0);
	private static final String EMPTY_STRING = ""; //$NON-NLS-1$

	private static final String COLOR_FORMAT = "#%02x%02x%02x"; //$NON-NLS-1$
	private static final String COLOR_SEPARATOR = ","; //$NON-NLS-1$

	private static final String SEPARATOR = ","; //$NON-NLS-1$

	private static final String NAMESPACE = null;

	private static final String TAG_SESSION = "session"; //$NON-NLS-1$
	private static final String TAG_DISCONNECTED = "disconnected"; //$NON-NLS-1$
	private static final String TAG_SET_PROJECT = "setProject"; //$NON-NLS-1$
	private static final String TAG_GET_PROJECTS = "projects"; //$NON-NLS-1$
	private static final String TAG_PROJECT = "project"; //$NON-NLS-1$
	private static final String TAG_GET_EVENTS = "events"; //$NON-NLS-1$
	private static final String TAG_EVENT = "event"; //$NON-NLS-1$
	private static final String TAG_RESOURCES = "resources"; //$NON-NLS-1$
	private static final String TAG_RESOURCE = "resource"; //$NON-NLS-1$
	private static final String TAG_ADDITIONAL = "additional"; //$NON-NLS-1$

	private static final String ATTR_ID = "id"; //$NON-NLS-1$
	private static final String ATTR_SESSION_ID = "sessionId"; //$NON-NLS-1$
	private static final String ATTR_PROJECT_ID = "projectId"; //$NON-NLS-1$
	private static final String ATTR_NAME = "name"; //$NON-NLS-1$
	private static final String ATTR_LOADED = "loaded"; //$NON-NLS-1$
	private static final String ATTR_START_HOUR = "startHour"; //$NON-NLS-1$
	private static final String ATTR_END_HOUR = "endHour"; //$NON-NLS-1$
	private static final String ATTR_DATE = "date"; //$NON-NLS-1$
	private static final String ATTR_NOTE = "note"; //$NON-NLS-1$
	private static final String ATTR_COLOR = "color"; //$NON-NLS-1$
	private static final String ATTR_LAST_UPDATE = "lastUpdate"; //$NON-NLS-1$
	private static final String ATTR_CREATION = "creation"; //$NON-NLS-1$
	private static final String ATTR_CATEGORY = "category"; //$NON-NLS-1$

	private static final String CATEGORY_GROUP = "trainee"; //$NON-NLS-1$
	private static final String CATEGORY_TEACHER = "instructor"; //$NON-NLS-1$
	private static final String CATEGORY_ROOM = "classroom"; //$NON-NLS-1$

	public static String parseConnectionFeed(InputStream in)
			throws XmlPullParserException, IOException {
		try {
			XmlPullParser parser = buildParser(in);
			parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_SESSION);
			return parser.getAttributeValue(NAMESPACE, ATTR_ID);
		} finally {
			in.close();
		}
	}

	public static String parseDisconnectionFeed(InputStream in)
			throws XmlPullParserException, IOException {
		try {
			XmlPullParser parser = buildParser(in);
			parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_DISCONNECTED);
			return parser.getAttributeValue(NAMESPACE, ATTR_SESSION_ID);
		} finally {
			in.close();
		}
	}

	public static void parseSetProjectFeed(InputStream in, String sessionId,
			long projectId) throws XmlPullParserException, IOException {
		try {
			XmlPullParser parser = buildParser(in);
			parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_SET_PROJECT);
			String resSessionId = parser.getAttributeValue(NAMESPACE,
					ATTR_SESSION_ID);
			long resProjectId = Long.parseLong(parser
					.getAttributeValue(NAMESPACE, ATTR_PROJECT_ID));

			// TODO Revoir les messages d'erreurs
			if (!sessionId.equals(resSessionId)) {
				Log.d("[parseSetProjectFeed]",
						String.format("SetProject error: wrong id. Expected '%s', got '%s'.",
								sessionId,
								resSessionId));
			}
			if (projectId != resProjectId) {
				Log.d("[parseSetProjectFeed]",
						String.format("SetProject error: wrong project id. Expected '%s', got '%s'.",
								projectId,
								resProjectId));
			}
		} finally {
			in.close();
		}
	}

	public static Cursor parseGetProjectsFeed(InputStream in)
			throws XmlPullParserException, IOException {
		try {
			final MatrixCursor projects = new MatrixCursor(
					ProjectQuery.PROJECTION);

			XmlPullParser parser = buildParser(in);
			parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_GET_PROJECTS);
			while (parser.next() != XmlPullParser.END_TAG) {
				if (parser.getEventType() != XmlPullParser.START_TAG) {
					continue;
				}
				projects.addRow(readProjectEntry(parser));
			}

			return projects;
		} finally {
			in.close();
		}
	}

	public static Cursor parseGetEventsFeed(InputStream in)
			throws XmlPullParserException, IOException {
		try {
			final MatrixCursor events = new MatrixCursor(LessonQuery.PROJECTION);

			XmlPullParser parser = buildParser(in);
			parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_GET_EVENTS);
			while (parser.next() != XmlPullParser.END_TAG) {
				if (parser.getEventType() != XmlPullParser.START_TAG) {
					continue;
				}
				events.addRow(readEventEntry(parser));
			}

			return events;
		} finally {
			in.close();
		}
	}

	/**
	 * @param in
	 * @return
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	private static XmlPullParser buildParser(InputStream in)
			throws XmlPullParserException, IOException {
		XmlPullParser parser = Xml.newPullParser();
		parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
		parser.setInput(in, null);
		parser.nextTag();
		return parser;
	}

	private static Iterable<?> readProjectEntry(XmlPullParser parser)
			throws XmlPullParserException, IOException {
		List<Object> project = AdeWebCollections.newArrayList();
		parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_PROJECT);

		project.add(DEFAULT_ID);
		project.add(parser.getAttributeValue(NAMESPACE, ATTR_NAME));
		project.add(parser.getAttributeValue(NAMESPACE, ATTR_ID));
		project.add(parser.getAttributeValue(NAMESPACE, ATTR_LOADED));

		parser.next();
		parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_PROJECT);

		return project;
	}

	private static Iterable<?> readEventEntry(XmlPullParser parser)
			throws XmlPullParserException, IOException {
		List<Object> event = AdeWebCollections.newArrayList();
		parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_EVENT);

		event.add(DEFAULT_ID);
		event.add(parser.getAttributeValue(NAMESPACE, ATTR_NAME));
		event.add(parser.getAttributeValue(NAMESPACE, ATTR_DATE));
		event.add(parser.getAttributeValue(NAMESPACE, ATTR_START_HOUR)); 
		event.add(parser.getAttributeValue(NAMESPACE, ATTR_END_HOUR));
		event.add(DEFAULT_ID);
		event.add(DEFAULT_INTEGER);
		event.add(EMPTY_STRING);
		event.add(EMPTY_STRING);
		event.add(EMPTY_STRING);
		event.add(readColor(parser));
		event.add(parser.getAttributeValue(NAMESPACE, ATTR_NOTE));
		event.add(parser.getAttributeValue(NAMESPACE, ATTR_ID));
		event.add(LessonConstants.DELETED_FALSE);
		event.add(parser.getAttributeValue(NAMESPACE, ATTR_LAST_UPDATE));
		event.add(null);
		event.add(null);
		event.add(null);
		event.add(null);
		event.add(parser.getAttributeValue(NAMESPACE, ATTR_CREATION));
		event.add(DEFAULT_INTEGER);
		event.add(Integer.valueOf(LessonConstants.LESSON_TYPE_LESSON));

		parser.nextTag();
		parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_RESOURCES);
		while (parser.next() != XmlPullParser.END_TAG) {
			if (parser.getEventType() != XmlPullParser.START_TAG) {
				continue;
			}
			readResourceEntry(parser, event);
		}
		parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_RESOURCES);

		parser.nextTag();
		parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_ADDITIONAL);
		skip(parser);
		parser.nextTag();
		parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_EVENT);

		return event;
	}

	private static void readResourceEntry(XmlPullParser parser,
			List<Object> event) throws XmlPullParserException, IOException {
		parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_RESOURCE);

		final String category = parser.getAttributeValue(NAMESPACE,
				ATTR_CATEGORY);
		if (category.equals(CATEGORY_GROUP)) {
			appendString(parser, event, LessonQuery.LESSON_GROUPS);
		} else if (category.equals(CATEGORY_ROOM)) {
			appendString(parser, event, LessonQuery.LESSON_ROOMS);
		} else if (category.equals(CATEGORY_TEACHER)) {
			appendString(parser, event, LessonQuery.LESSON_TEACHERS);
		}

		parser.next();
		parser.require(XmlPullParser.END_TAG, NAMESPACE, TAG_RESOURCE);
	}

	private static void skip(XmlPullParser parser)
			throws XmlPullParserException, IOException {
		if (parser.getEventType() != XmlPullParser.START_TAG) {
			throw new IllegalStateException();
		}
		int depth = 1;
		while (depth != 0) {
			switch (parser.next()) {
			case XmlPullParser.END_TAG:
				depth--;
				break;
			case XmlPullParser.START_TAG:
				depth++;
				break;
			default:
			}
		}
	}

	/**
	 * @param parser
	 * @param event
	 * @param position
	 */
	private static void appendString(XmlPullParser parser, List<Object> event,
			int position) {
		String base = (String) event.get(position);
		if (!TextUtils.isEmpty(base)) {
			base += SEPARATOR;
		}
		base += parser.getAttributeValue(NAMESPACE, ATTR_NAME);
	}

	/**
	 * @param parser
	 * @return
	 */
	private static String readColor(XmlPullParser parser) {
		String[] stringValues = parser.getAttributeValue(NAMESPACE, ATTR_COLOR)
				.split(COLOR_SEPARATOR);
		return String.format(COLOR_FORMAT,
				Integer.valueOf(stringValues[0]),
				Integer.valueOf(stringValues[1]),
				Integer.valueOf(stringValues[2]));
	}

}// class AdeWebXmlParser